package strain.domain.interval_set_alt;

import static strain.domain.CompareWrapper.equal;
import static strain.domain.CompareWrapper.less;
import static strain.domain.CompareWrapper.lessOrEqual;
import static strain.domain.CompareWrapper.max;
import static strain.domain.CompareWrapper.min;

import java.util.Comparator;

public class Interval<T extends Comparable<T>> {
    
    public static class LowerBoundComparator<T extends Comparable<T>> implements Comparator<Interval<T>>{
        public int compare(final Interval<T> i1, final Interval<T> i2) {
            return i1.getLow().compareTo(i2.getLow());
        }
    }
    
    private final IntervalBound<T> low;
    private final IntervalBound<T> high;
    
    public Interval(final IntervalBound<T> low, final IntervalBound<T> high){
        if (less(high.getBound(), low.getBound()) || low.isUpper() || high.isLower()){
            throw new IllegalArgumentException("invalid bounds for Interval: low=" + low + "; high=" + high);
        }
        this.low = low;
        this.high = high;
    }
    
    public Interval(final T low, final T high, final boolean lowInclusive, final boolean highInclusive){
        this(new IntervalBound<T>(low, true, lowInclusive), new IntervalBound<T>(high, false, highInclusive));
    }
    
    public Interval(final T low, final T high, final boolean bothInclusive){
        this(low, high, bothInclusive, bothInclusive);
    }
    
    public Interval(final T low, final T high){
        this(low, high, true);
    }

    public IntervalBound<T> getLow(){
        return this.low;
    }
    
    public IntervalBound<T> getHigh(){
        return this.high;
    }
    
    public boolean contains(final T t){
        return (this.low.contains(t) && this.high.contains(t));
    }
    
    public boolean isAfter(final T t){
        return !this.low.contains(t);
    }
    
    public boolean isBefore(final T t){
        return !this.high.contains(t);
    }
    
    public boolean isAfter(final Interval<T> interval){
        return less(this.low, interval.high);
    }
    
    public boolean isBefore(final Interval<T> interval){
        return less(this.high, interval.low);
    }
    
    public boolean contains(final Interval<T> interval){
        return (lessOrEqual(this.low, interval.low) && lessOrEqual(interval.high, this.high));
    }
    
    public boolean abuts(final Interval<T> interval){
        return equal(this.low, interval.high.complement()) || equal(this.high, interval.low.complement());
    }
    
    public boolean overlaps(final Interval<T> interval){
        return !(this.isBefore(interval) || interval.isBefore(this));
    }
    
    public Interval<T> overlap(final Interval<T> interval){
        IntervalBound<T> l = max(this.low, interval.low);
        IntervalBound<T> h = min(this.high, interval.high);
        if (lessOrEqual(l,h)){
            return new Interval<T>(l, h);
        }
        
        return null;
    }
    
    public Interval<T> gap(final Interval<T> interval){
        IntervalBound<T> l = min(this.high, interval.high);
        IntervalBound<T> h = max(this.low, interval.low);
        if (lessOrEqual(l,h)){
            return new Interval<T>(l, h);
        }

        return null;
    }
    
    public Interval<T> hull(final Interval<T> interval){
        IntervalBound<T> l = min(this.low, interval.low);
        IntervalBound<T> h = max(this.high, interval.high);
        if (lessOrEqual(l,h)){
            return new Interval<T>(l, h);
        }

        return null;
    }
    
    public Interval<T> withStart(final T t, final boolean inclusive){
        return new Interval<T>(new IntervalBound<T>(t, true, inclusive), this.high);
    }
    
    public Interval<T> withStart(final T t){
        return withStart(t, true);
    }
    
    public Interval<T> withEnd(final T t, final boolean inclusive){
        return new Interval<T>(this.low, new IntervalBound<T>(t, false, inclusive));
    }
    
    public Interval<T> withEnd(final T t){
        return withEnd(t, true);
    }
    
    public IntervalSet<T> asIntervalSet(){
        return new IntervalSet<T>(this);
    }
    
    @Override
    public String toString(){
        return this.low.toString() + " " + this.high.toString();
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(final Object o){
        if (this == o) return true;
        if ((o == null) || !(o.getClass().equals(this.getClass()))) return false;
        
        Interval<T> i = (Interval<T>) o;
        
        boolean result = (this.low == null) ? (i.low == null) : (this.low.equals(i.low));
        result = result && (this.high == null) ? (i.high == null) : (this.high.equals(i.high));
        return result;
    }
    
    @Override
    public int hashCode(){
        int result = (this.low == null) ? (0) : (this.low.hashCode());
        result += result * 31 + ((this.high == null) ? (0) : (this.high.hashCode()));
        return result;
    }
}
